//===--- ArgsToFrontendOptionsConverter -----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/frontend/internal/ArgsToFrontendOptionsConverter.h"
#include "polarphp/frontend/internal/ArgsToFrontendInputsConverter.h"
#include "polarphp/frontend/internal/ArgsToFrontendOutputsConverter.h"
#include "polarphp/ast/DiagnosticsFrontend.h"
#include "polarphp/frontend/Frontend.h"
#include "polarphp/option/Options.h"
#include "polarphp/option/SanitizerOptions.h"
#include "polarphp/llparser/Lexer.h"
#include "polarphp/global/NameStrings.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"

using namespace polar;
using namespace llvm::opt;
using namespace polar::llparser;

// This is a separate function so that it shows up in stack traces.
LLVM_ATTRIBUTE_NOINLINE
static void debugFailWithAssertion() {
   // This assertion should always fail, per the user's request, and should
   // not be converted to llvm_unreachable.
   assert(0 && "This is an assertion!");
}

// This is a separate function so that it shows up in stack traces.
LLVM_ATTRIBUTE_NOINLINE
static void debugFailWithCrash() { LLVM_BUILTIN_TRAP; }

bool ArgsToFrontendOptionsConverter::convert(
   SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> *buffers) {
   using namespace options;

   handleDebugCrashGroupArguments();

   if (const Arg *A = Args.getLastArg(OPT_dump_api_path)) {
      Opts.DumpAPIPath = A->getValue();
   }
   if (const Arg *A = Args.getLastArg(OPT_group_info_path)) {
      Opts.GroupInfoPath = A->getValue();
   }
   if (const Arg *A = Args.getLastArg(OPT_index_store_path)) {
      Opts.IndexStorePath = A->getValue();
   }
   if (const Arg *A = Args.getLastArg(OPT_prebuilt_module_cache_path)) {
      Opts.PrebuiltModuleCachePath = A->getValue();
   }

   Opts.IndexSystemModules |= Args.hasArg(OPT_index_system_modules);

   Opts.EmitVerbosePIL |= Args.hasArg(OPT_emit_verbose_pil);
   Opts.EmitSortedPIL |= Args.hasArg(OPT_emit_sorted_pil);

   Opts.EnableTesting |= Args.hasArg(OPT_enable_testing);
   Opts.EnablePrivateImports |= Args.hasArg(OPT_enable_private_imports);
   Opts.EnableLibraryEvolution |= Args.hasArg(OPT_enable_library_evolution);

   // FIXME: Remove this flag
   Opts.EnableLibraryEvolution |= Args.hasArg(OPT_enable_resilience);

   Opts.EnableImplicitDynamic |= Args.hasArg(OPT_enable_implicit_dynamic);

   Opts.TrackSystemDeps |= Args.hasArg(OPT_track_system_dependencies);

   Opts.SerializeModuleInterfaceDependencyHashes |=
      Args.hasArg(OPT_serialize_module_interface_dependency_hashes);

   Opts.RemarkOnRebuildFromModuleInterface |=
      Args.hasArg(OPT_Rmodule_interface_rebuild);

   computePrintStatsOptions();
   computeDebugTimeOptions();
   computeTBDOptions();

   Opts.CheckOnoneSupportCompleteness = Args.hasArg(OPT_check_onone_completeness);

   Opts.DebuggerTestingTransform = Args.hasArg(OPT_debugger_testing_transform);

   computePlaygroundOptions();

   // This can be enabled independently of the playground transform.
   Opts.PCMacro |= Args.hasArg(OPT_pc_macro);

   Opts.ParseStdlib |= Args.hasArg(OPT_parse_stdlib);

   Opts.IgnoreSwiftSourceInfo |= Args.hasArg(OPT_ignore_module_source_info);
   computeHelpOptions();

   if (const Arg *A = Args.getLastArg(OPT_verify_generic_signatures)) {
      Opts.VerifyGenericSignaturesInModule = A->getValue();
   }

   computeDumpScopeMapLocations();

   Optional<FrontendInputsAndOutputs> inputsAndOutputs =
      ArgsToFrontendInputsConverter(Diags, Args).convert(buffers);

   // None here means error, not just "no inputs". Propagage unconditionally.
   if (!inputsAndOutputs)
      return true;

   // InputsAndOutputs can only get set up once; if it was set already when we
   // entered this function, we should not set it again (and should assert this
   // is not being done). Further, the computeMainAndSupplementaryOutputFilenames
   // call below needs to only happen when there was a new InputsAndOutputs,
   // since it clobbers the existing one rather than adding to it.
   bool HaveNewInputsAndOutputs = false;
   if (Opts.InputsAndOutputs.hasInputs()) {
      assert(!inputsAndOutputs->hasInputs());
   } else {
      HaveNewInputsAndOutputs = true;
      Opts.InputsAndOutputs = std::move(inputsAndOutputs).getValue();
   }

   if (Opts.RequestedAction == FrontendOptions::ActionType::NoneAction) {
      Opts.RequestedAction = determineRequestedAction(Args);
   }

   if (Opts.RequestedAction == FrontendOptions::ActionType::Immediate &&
       Opts.InputsAndOutputs.hasPrimaryInputs()) {
      Diags.diagnose(SourceLoc(), diag::error_immediate_mode_primary_file);
      return true;
   }

   if (setUpInputKindAndImmediateArgs())
      return true;

   if (computeModuleName())
      return true;

   if (HaveNewInputsAndOutputs &&
       computeMainAndSupplementaryOutputFilenames())
      return true;

   if (checkUnusedSupplementaryOutputPaths())
      return true;

   if (FrontendOptions::doesActionGenerateIR(Opts.RequestedAction)
       && Args.hasArg(OPT_experimental_skip_non_inlinable_function_bodies)) {
      Diags.diagnose(SourceLoc(), diag::cannot_emit_ir_skipping_function_bodies);
      return true;
   }

   if (const Arg *A = Args.getLastArg(OPT_module_link_name))
      Opts.ModuleLinkName = A->getValue();

   if (const Arg *A = Args.getLastArg(OPT_serialize_debugging_options,
                                      OPT_no_serialize_debugging_options)) {
      Opts.SerializeOptionsForDebugging =
         A->getOption().matches(OPT_serialize_debugging_options);
   }

   Opts.EnableSourceImport |= Args.hasArg(OPT_enable_source_import);
   Opts.ImportUnderlyingModule |= Args.hasArg(OPT_import_underlying_module);
   Opts.EnableSerializationNestedTypeLookupTable &=
      !Args.hasArg(OPT_disable_serialization_nested_type_lookup_table);

   computeImportObjCHeaderOptions();
   computeImplicitImportModuleNames();
   computeLLVMArgs();

   return false;
}

void ArgsToFrontendOptionsConverter::handleDebugCrashGroupArguments() {
   using namespace options;

   if (const Arg *A = Args.getLastArg(OPT_debug_crash_Group)) {
      Option Opt = A->getOption();
      if (Opt.matches(OPT_debug_assert_immediately)) {
         debugFailWithAssertion();
      } else if (Opt.matches(OPT_debug_crash_immediately)) {
         debugFailWithCrash();
      } else if (Opt.matches(OPT_debug_assert_after_parse)) {
         // Set in FrontendOptions
         Opts.CrashMode = FrontendOptions::DebugCrashMode::AssertAfterParse;
      } else if (Opt.matches(OPT_debug_crash_after_parse)) {
         // Set in FrontendOptions
         Opts.CrashMode = FrontendOptions::DebugCrashMode::CrashAfterParse;
      } else {
         llvm_unreachable("Unknown debug_crash_Group option!");
      }
   }
}

void ArgsToFrontendOptionsConverter::computePrintStatsOptions() {
   using namespace options;
   Opts.PrintStats |= Args.hasArg(OPT_print_stats);
   Opts.PrintClangStats |= Args.hasArg(OPT_print_clang_stats);
#if defined(NDEBUG) && !defined(LLVM_ENABLE_STATS)
   if (Opts.PrintStats || Opts.PrintClangStats)
    Diags.diagnose(SourceLoc(), diag::stats_disabled);
#endif
}

void ArgsToFrontendOptionsConverter::computeDebugTimeOptions() {
   using namespace options;
   Opts.DebugTimeCompilation |= Args.hasArg(OPT_debug_time_compilation);

   if (const Arg *A = Args.getLastArg(OPT_stats_output_dir)) {
      Opts.StatsOutputDir = A->getValue();
      if (Args.getLastArg(OPT_trace_stats_events)) {
         Opts.TraceStats = true;
      }
      if (Args.getLastArg(OPT_profile_stats_events)) {
         Opts.ProfileEvents = true;
      }
      if (Args.getLastArg(OPT_profile_stats_entities)) {
         Opts.ProfileEntities = true;
      }
   }
}

void ArgsToFrontendOptionsConverter::computeTBDOptions() {
   using namespace options;
   if (const Arg *A = Args.getLastArg(OPT_validate_tbd_against_ir_EQ)) {
      using Mode = FrontendOptions::TBDValidationMode;
      StringRef value = A->getValue();
      if (value == "none") {
         Opts.ValidateTBDAgainstIR = Mode::None;
      } else if (value == "missing") {
         Opts.ValidateTBDAgainstIR = Mode::MissingFromTBD;
      } else if (value == "all") {
         Opts.ValidateTBDAgainstIR = Mode::All;
      } else {
         Diags.diagnose(SourceLoc(), diag::error_unsupported_option_argument,
                        A->getOption().getPrefixedName(), value);
      }
   }
}

void ArgsToFrontendOptionsConverter::computePlaygroundOptions() {
   using namespace options;
   Opts.PlaygroundTransform |= Args.hasArg(OPT_playground);
   if (Args.hasArg(OPT_disable_playground_transform))
      Opts.PlaygroundTransform = false;
   Opts.PlaygroundHighPerformance |=
      Args.hasArg(OPT_playground_high_performance);
}

void ArgsToFrontendOptionsConverter::computeHelpOptions() {
   using namespace options;
   if (const Arg *A = Args.getLastArg(OPT_help, OPT_help_hidden)) {
      if (A->getOption().matches(OPT_help)) {
         Opts.PrintHelp = true;
      } else if (A->getOption().matches(OPT_help_hidden)) {
         Opts.PrintHelpHidden = true;
      } else {
         llvm_unreachable("Unknown help option parsed");
      }
   }
}

void ArgsToFrontendOptionsConverter::computeDumpScopeMapLocations() {
   using namespace options;
   const Arg *A = Args.getLastArg(OPT_modes_Group);
   if (!A || !A->getOption().matches(OPT_dump_scope_maps))
      return;
   StringRef value = A->getValue();
   if (value == "expanded") {
      // Note: fully expanded the scope map.
      return;
   }
   // Parse a comma-separated list of line:column for lookups to
   // perform (and dump the result of).
   SmallVector<StringRef, 4> locations;
   value.split(locations, ',');

   bool invalid = false;
   for (auto location : locations) {
      auto lineColumnStr = location.split(':');
      unsigned line, column;
      if (lineColumnStr.first.getAsInteger(10, line) ||
          lineColumnStr.second.getAsInteger(10, column)) {
         Diags.diagnose(SourceLoc(), diag::error_invalid_source_location_str,
                        location);
         invalid = true;
         continue;
      }
      Opts.DumpScopeMapLocations.push_back({line, column});
   }

   if (!invalid && Opts.DumpScopeMapLocations.empty())
      Diags.diagnose(SourceLoc(), diag::error_no_source_location_scope_map);
}

FrontendOptions::ActionType
ArgsToFrontendOptionsConverter::determineRequestedAction(const ArgList &args) {
   using namespace options;
   const Arg *A = args.getLastArg(OPT_modes_Group);
   if (!A) {
      // We don't have a mode, so determine a default.
      if (args.hasArg(OPT_emit_module, OPT_emit_module_path)) {
         // We've been told to emit a module, but have no other mode indicators.
         // As a result, put the frontend into EmitModuleOnly mode.
         // (Setting up module output will be handled below.)
         return FrontendOptions::ActionType::EmitModuleOnly;
      }
      return FrontendOptions::ActionType::NoneAction;
   }
   Option Opt = A->getOption();
   if (Opt.matches(OPT_emit_object))
      return FrontendOptions::ActionType::EmitObject;
   if (Opt.matches(OPT_emit_assembly))
      return FrontendOptions::ActionType::EmitAssembly;
   if (Opt.matches(OPT_emit_ir))
      return FrontendOptions::ActionType::EmitIR;
   if (Opt.matches(OPT_emit_bc))
      return FrontendOptions::ActionType::EmitBC;
   if (Opt.matches(OPT_emit_pil))
      return FrontendOptions::ActionType::EmitPIL;
   if (Opt.matches(OPT_emit_pilgen))
      return FrontendOptions::ActionType::EmitPILGen;
   if (Opt.matches(OPT_emit_sib))
      return FrontendOptions::ActionType::EmitPIB;
   if (Opt.matches(OPT_emit_sibgen))
      return FrontendOptions::ActionType::EmitPIBGen;
   if (Opt.matches(OPT_emit_pch))
      return FrontendOptions::ActionType::EmitPCH;
   if (Opt.matches(OPT_emit_imported_modules))
      return FrontendOptions::ActionType::EmitImportedModules;
   if (Opt.matches(OPT_parse))
      return FrontendOptions::ActionType::Parse;
   if (Opt.matches(OPT_resolve_imports))
      return FrontendOptions::ActionType::ResolveImports;
   if (Opt.matches(OPT_typecheck))
      return FrontendOptions::ActionType::Typecheck;
   if (Opt.matches(OPT_dump_parse))
      return FrontendOptions::ActionType::DumpParse;
   if (Opt.matches(OPT_dump_ast))
      return FrontendOptions::ActionType::DumpAST;
   if (Opt.matches(OPT_emit_syntax))
      return FrontendOptions::ActionType::EmitSyntax;
   if (Opt.matches(OPT_merge_modules))
      return FrontendOptions::ActionType::MergeModules;
   if (Opt.matches(OPT_dump_scope_maps))
      return FrontendOptions::ActionType::DumpScopeMaps;
   if (Opt.matches(OPT_dump_type_refinement_contexts))
      return FrontendOptions::ActionType::DumpTypeRefinementContexts;
   if (Opt.matches(OPT_dump_interface_hash))
      return FrontendOptions::ActionType::DumpInterfaceHash;
   if (Opt.matches(OPT_dump_type_info))
      return FrontendOptions::ActionType::DumpTypeInfo;
   if (Opt.matches(OPT_print_ast))
      return FrontendOptions::ActionType::PrintAST;
   if (Opt.matches(OPT_emit_pcm))
      return FrontendOptions::ActionType::EmitPCM;
   if (Opt.matches(OPT_dump_pcm))
      return FrontendOptions::ActionType::DumpPCM;

   if (Opt.matches(OPT_repl) || Opt.matches(OPT_deprecated_integrated_repl))
      return FrontendOptions::ActionType::REPL;
   if (Opt.matches(OPT_interpret))
      return FrontendOptions::ActionType::Immediate;
   if (Opt.matches(OPT_compile_module_from_interface))
      return FrontendOptions::ActionType::CompileModuleFromInterface;

   llvm_unreachable("Unhandled mode option");
}

bool ArgsToFrontendOptionsConverter::setUpInputKindAndImmediateArgs() {
   using namespace options;
   bool treatAsPIL =
      Args.hasArg(OPT_parse_pil) || Opts.InputsAndOutputs.shouldTreatAsPIL();

   if (Opts.InputsAndOutputs.verifyInputs(
      Diags, treatAsPIL,
      Opts.RequestedAction == FrontendOptions::ActionType::REPL,
      Opts.RequestedAction == FrontendOptions::ActionType::NoneAction)) {
      return true;
   }
   if (Opts.RequestedAction == FrontendOptions::ActionType::Immediate) {
      Opts.ImmediateArgv.push_back(
         Opts.InputsAndOutputs.getFilenameOfFirstInput()); // argv[0]
      if (const Arg *A = Args.getLastArg(OPT__DASH_DASH)) {
         for (unsigned i = 0, e = A->getNumValues(); i != e; ++i) {
            Opts.ImmediateArgv.push_back(A->getValue(i));
         }
      }
   }

   if (treatAsPIL)
      Opts.InputKind = InputFileKind::PIL;
   else if (Opts.InputsAndOutputs.shouldTreatAsLLVM())
      Opts.InputKind = InputFileKind::LLVM;
   else if (Opts.InputsAndOutputs.shouldTreatAsModuleInterface())
      Opts.InputKind = InputFileKind::PHPModuleInterface;
   else if (Args.hasArg(OPT_parse_as_library))
      Opts.InputKind = InputFileKind::PHPLibrary;
   else if (Opts.RequestedAction == FrontendOptions::ActionType::REPL)
      Opts.InputKind = InputFileKind::PHPREPL;
   else
      Opts.InputKind = InputFileKind::PHP;

   return false;
}

bool ArgsToFrontendOptionsConverter::computeModuleName() {
   const Arg *A = Args.getLastArg(options::OPT_module_name);
   if (A) {
      Opts.ModuleName = A->getValue();
   } else if (Opts.ModuleName.empty()) {
      // The user did not specify a module name, so determine a default fallback
      // based on other options.

      // Note: this code path will only be taken when running the frontend
      // directly; the driver should always pass -module-name when invoking the
      // frontend.
      if (computeFallbackModuleName())
         return true;
   }

   if (Lexer::isIdentifier(Opts.ModuleName) &&
       (Opts.ModuleName != STDLIB_NAME || Opts.ParseStdlib)) {
      return false;
   }
   if (!FrontendOptions::needsProperModuleName(Opts.RequestedAction) ||
       Opts.isCompilingExactlyOnePolarphpFile()) {
      Opts.ModuleName = "main";
      return false;
   }
   auto DID = (Opts.ModuleName == STDLIB_NAME) ? diag::error_stdlib_module_name
                                               : diag::error_bad_module_name;
   Diags.diagnose(SourceLoc(), DID, Opts.ModuleName, A == nullptr);
   Opts.ModuleName = "__bad__";
   return false; // FIXME: Must continue to run to pass the tests, but should not
   // have to.
}

bool ArgsToFrontendOptionsConverter::computeFallbackModuleName() {
   if (Opts.RequestedAction == FrontendOptions::ActionType::REPL) {
      // Default to a module named "REPL" if we're in REPL mode.
      Opts.ModuleName = "REPL";
      return false;
   }
   // In order to pass some tests, must leave ModuleName empty.
   if (!Opts.InputsAndOutputs.hasInputs()) {
      Opts.ModuleName = StringRef();
      // FIXME: This is a bug that should not happen, but does in tests.
      // The compiler should bail out earlier, where "no frontend action was
      // selected".
      return false;
   }
   Optional<std::vector<std::string>> outputFilenames =
      OutputFilesComputer::getOutputFilenamesFromCommandLineOrFilelist(Args,
                                                                       Diags);

   std::string nameToStem =
      outputFilenames && outputFilenames->size() == 1 &&
      outputFilenames->front() != "-" &&
      !llvm::sys::fs::is_directory(outputFilenames->front())
      ? outputFilenames->front()
      : Opts.InputsAndOutputs.getFilenameOfFirstInput();

   Opts.ModuleName = llvm::sys::path::stem(nameToStem);
   return false;
}

bool ArgsToFrontendOptionsConverter::
computeMainAndSupplementaryOutputFilenames() {
   std::vector<std::string> mainOutputs;
   std::vector<SupplementaryOutputPaths> supplementaryOutputs;
   const bool hadError = ArgsToFrontendOutputsConverter(
      Args, Opts.ModuleName, Opts.InputsAndOutputs, Diags)
      .convert(mainOutputs, supplementaryOutputs);
   if (hadError)
      return true;
   Opts.InputsAndOutputs.setMainAndSupplementaryOutputs(mainOutputs,
                                                        supplementaryOutputs);
   return false;
}

bool ArgsToFrontendOptionsConverter::checkUnusedSupplementaryOutputPaths()
const {
   if (!FrontendOptions::canActionEmitDependencies(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasDependenciesPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_dependencies);
      return true;
   }
   if (!FrontendOptions::canActionEmitReferenceDependencies(Opts.RequestedAction)
       && Opts.InputsAndOutputs.hasReferenceDependenciesPath()) {
      Diags.diagnose(SourceLoc(),
                     diag::error_mode_cannot_emit_reference_dependencies);
      return true;
   }
   if (!FrontendOptions::canActionEmitPHPRanges(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasPHPRangesPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_php_ranges);
      return true;
   }
   if (!FrontendOptions::canActionEmitCompiledSource(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasCompiledSourcePath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_compiled_source);
      return true;
   }
   if (!FrontendOptions::canActionEmitObjCHeader(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasObjCHeaderOutputPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_header);
      return true;
   }
   if (!FrontendOptions::canActionEmitLoadedModuleTrace(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasLoadedModuleTracePath()) {
      Diags.diagnose(SourceLoc(),
                     diag::error_mode_cannot_emit_loaded_module_trace);
      return true;
   }
   if (!FrontendOptions::canActionEmitModule(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasModuleOutputPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_module);
      return true;
   }
   if (!FrontendOptions::canActionEmitModuleDoc(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasModuleDocOutputPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_module_doc);
      return true;
   }
   // If we cannot emit module doc, we cannot emit source information file either.
   if (!FrontendOptions::canActionEmitModuleDoc(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasModuleSourceInfoOutputPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_module_source_info);
      return true;
   }
   if (!FrontendOptions::canActionEmitInterface(Opts.RequestedAction) &&
       Opts.InputsAndOutputs.hasModuleInterfaceOutputPath()) {
      Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_interface);
      return true;
   }
   return false;
}

void ArgsToFrontendOptionsConverter::computeImportObjCHeaderOptions() {
   using namespace options;
   if (const Arg *A = Args.getLastArgNoClaim(OPT_import_objc_header)) {
      Opts.ImplicitObjCHeaderPath = A->getValue();
      Opts.SerializeBridgingHeader |= !Opts.InputsAndOutputs.hasPrimaryInputs();
   }
}

void ArgsToFrontendOptionsConverter::computeImplicitImportModuleNames() {
   using namespace options;
   for (const Arg *A : Args.filtered(OPT_import_module)) {
      Opts.ImplicitImportModuleNames.push_back(A->getValue());
   }
}

void ArgsToFrontendOptionsConverter::computeLLVMArgs() {
   using namespace options;
   for (const Arg *A : Args.filtered(OPT_Xllvm)) {
      Opts.LLVMArgs.push_back(A->getValue());
   }
}
